{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "87476789-7fe5-4102-9bc4-bb2ee0eb62d3",
   "metadata": {},
   "source": [
    "### 代码练习：用KNN分辨猫和狗"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55567332-ff92-4632-ada6-ca540bda227a",
   "metadata": {},
   "source": [
    "#### create_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81913c12-3384-4494-bdf4-a4c62ab93850",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.model_selection import train_test_split\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pickle\n",
    "import os\n",
    "import time\n",
    "import cv2 as cv\n",
    "\n",
    "#文字\n",
    "plt.rcParams['font.sans-serif'] = ['SimHei']\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "# 判断文件是否存在，不存在创建，并把 content 以二进制写进 filename\n",
    "def write(filename, content):\n",
    "    if not os.path.isfile(filename):\n",
    "        with open(filename, 'wb') as f:\n",
    "            pickle.dump(content, f)\n",
    "\n",
    "# 读取filename\n",
    "def read(filename):\n",
    "    with open(filename, 'rb') as f:\n",
    "        file = pickle.load(f)\n",
    "    return file\n",
    "\n",
    "# 获取最大K，最大acc, 所有的acc\n",
    "def get_max_K(X_train, X_test, y_train, y_test):\n",
    "    K_max_index = 0\n",
    "    acc_max = 0\n",
    "    acc_score = []\n",
    "    for k in range(1, 301):\n",
    "        knn = KNeighborsClassifier(n_neighbors=k)\n",
    "        knn.fit(X_train, y_train)\n",
    "        acc = knn.score(X_test, y_test)\n",
    "        acc_score.append(acc)\n",
    "        if acc > acc_max:\n",
    "            acc_max = acc\n",
    "            K_max_index = k\n",
    "    return K_max_index, acc_max, acc_score\n",
    "\n",
    "#开始时间\n",
    "start = time.time()\n",
    "\n",
    "out1_path = 'X'\n",
    "if not os.path.isfile(out1_path):\n",
    "    X = []\n",
    "    y = []\n",
    "    for f in os.listdir():\n",
    "        if \".jpg\" not in f:  # 如果不是jpg图像文件，则略过\n",
    "            continue\n",
    "        img = cv.imread(f, 0)  # 以灰度图读取\n",
    "        new_img = cv.resize(img, (32, 32), interpolation=cv.INTER_NEAREST)  # 用最近邻内插法缩放到指定大小\n",
    "        X.append(new_img.ravel())\n",
    "        y.append(f.split('.')[0])\n",
    "    write('X', X)  # 保存X\n",
    "    write('y', y)  # 保存y\n",
    "\n",
    "# 获取 X y\n",
    "X = read('X')\n",
    "y = read('y')\n",
    "\n",
    "# 拆分数据\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)\n",
    "\n",
    "# 获取最大K， 最大acc\n",
    "K_max_index, acc_max, acc_score = get_max_K(X_train, X_test, y_train, y_test)\n",
    "\n",
    "# 使用最大K训练模型\n",
    "knn = KNeighborsClassifier(n_neighbors=K_max_index)\n",
    "knn.fit(X_train, y_train)\n",
    "acc = knn.score(X_test, y_test)\n",
    "\n",
    "# 保存 最大K训练出的模型 knn,X_train\n",
    "write('knn', knn)\n",
    "write('X_train', X_train)\n",
    "\n",
    "#结束时间\n",
    "end = time.time()\n",
    "times = end - start\n",
    "\n",
    "plt.figure(figsize=(10, 8))\n",
    "plt.plot(np.arange(1, 301), acc_score,  # 数据部分\n",
    "         color='blue', linestyle='-')  # 线外观\n",
    "plt.title('duration:{0:.2f}minutes, max accuracy:{1:.2f}%时(K={2})\\n druation:{3}'.format(duration / 60, acc_max * 100, K_max_index, duration))\n",
    "plt.savefig('acc_300.pdf')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4d4cb56-3e4a-4f8f-8987-d64de442c699",
   "metadata": {},
   "source": [
    "参考答案"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2deb8f54-4bc3-4c1f-9279-0177d4c653dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.model_selection import train_test_split\n",
    "import numpy as np\n",
    "import pickle # <-- 这个一定要学会\n",
    "import cv2 as cv\n",
    "import matplotlib.pyplot as plt\n",
    "import time\n",
    "from os.path import exists\n",
    "from imutils import paths # 使用这个包可以快速的处理一些与图像相关的文件处理工作\n",
    "import os\n",
    "\n",
    "############################## 函数声明部分 ##############################\n",
    "# 显示进度条\n",
    "def progress_bar(s,i):\n",
    "    print(\"\\r\", end=\"\")  # 输出位置回到行首\n",
    "    print(s+\": {}%: \".format(i), \"▋\" * (i // 2), end=\"\") \n",
    "\n",
    "# 把图像转化成指定长度的特征向量\n",
    "def createImageFeatures(image, size=(32, 32)):\n",
    "    image = cv.resize(image, size)\n",
    "    pixel_list = image.flatten()\n",
    "    return pixel_list\n",
    "\n",
    "# 获取/创建包含有所有图像的X和y\n",
    "def createXY(folder):\n",
    "    # 如果文件已经有了，就不再重新创建了，而直接载入\n",
    "    if exists(\"X\") and exists(\"y\"):\n",
    "        with open(\"X\", 'rb') as f:\n",
    "            X = pickle.load(f)\n",
    "        with open(\"y\", 'rb') as f:\n",
    "            y = pickle.load(f)\n",
    "        return X,y\n",
    "\n",
    "    print(\"读取所有图像，生成X和y\")\n",
    "    image_paths = list(paths.list_images(folder)) #从folder中获得所有的图像文件列表\n",
    "\n",
    "    X = []\n",
    "    y = []\n",
    "    i=1\n",
    "    for image_path in image_paths:\n",
    "        progress_bar(\"读取图像\",i*100//len(image_paths))\n",
    "        image = cv.imread(image_path,0)\n",
    "        \n",
    "        # .../dogs-vs-cats/training_data/cat.9999.jpg\n",
    "        # 下面代码从以上这种完整路径中首先获得按照/分割的最后一个元素，也就是文件名 cat.9999.jpg\n",
    "        # 然后再获得按照.分割的数组中的第一个元素，也就是 cat\n",
    "        label = image_path.split(os.path.sep)[-1].split(\".\")[0]\n",
    "        pixels = createImageFeatures(image)\n",
    "        X.append(pixels)\n",
    "        y.append(label) # 注意，更优秀的方式：把cat->0,dog->1\n",
    "        i=i+1\n",
    "    with open(\"X\", 'wb') as f:\n",
    "        pickle.dump(X, f)\n",
    "        print(\"已生成X, 结构:\",np.shape(X))\n",
    "    with open(\"y\", 'wb') as f:\n",
    "        pickle.dump(y, f) \n",
    "        print(\"已生成y, 结构:\",np.shape(y))\n",
    "    return X,y\n",
    "\n",
    "############################## 程序逻辑部分 ##############################\n",
    "# 1. 创建/读取X和y\n",
    "X,y = createXY(\".\")\n",
    "print(\"成功读取X和y\")\n",
    "print(\"X的形状:\",np.shape(X))\n",
    "print(\"y的形状:\",np.shape(y))\n",
    "\n",
    "# 2. 分割X和y\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)\n",
    "with open(\"X_train\", 'wb') as f:\n",
    "    pickle.dump(X_train,f)\n",
    "    print(\"已生成：X_train, 形状:\",np.shape(X_train))\n",
    "\n",
    "# 3. 搜索最佳K\n",
    "print(\"开始搜索最佳K:\")\n",
    "start = time.time() # <--- 开始计时\n",
    "acc_list=[]\n",
    "max_test=300\n",
    "for k in range(1,max_test+1):\n",
    "    clf = KNeighborsClassifier(n_neighbors=k) # 1. 创建分类器\n",
    "    clf.fit(X_train, y_train)                 # 2. fit\n",
    "    acc = clf.score(X_test, y_test)           # 3. score\n",
    "    print(\"K={0} 准确率: {1:.2f}%\".format(k, acc * 100))\n",
    "    acc_list.append(acc) # 纪律所有的准确率\n",
    "end = time.time() # <--- 结束计时\n",
    "\n",
    "# 保存最大准确率对应的knn\n",
    "max_acc_k=np.argmax(acc_list)+1 # 利用最大准确率的位置得到对应的k：max_acc_k\n",
    "# 训练出 max_acc_k 对应的 knn 模型\n",
    "clf = KNeighborsClassifier(n_neighbors=max_acc_k).fit(X_train, y_train)\n",
    "with open(\"knn\", 'wb') as f: \n",
    "    pickle.dump(clf, f)\n",
    "    print(\"已保存具有最大准确率knn\")\n",
    "\n",
    "# 保存测试结果到pdf\n",
    "plt.plot(range(1,max_test+1),acc_list)\n",
    "plt.title(\"duration: {0:.2f} minutes, max accuracy: {1:.2f}% (k={2})\".format((end - start)/60,max(acc_list) * 100,max_acc_k))\n",
    "plt.savefig(\"acc_{0}.pdf\".format(max_test))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e7a95a5-cd78-49f0-8404-adbd880d543b",
   "metadata": {},
   "source": [
    "#### test_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0af6f260-a65f-4ecb-a8bb-cd9208deadac",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import pickle\n",
    "import numpy as np\n",
    "import cv2 as cv\n",
    "\n",
    "index = np.random.randint(1, 12501, 2)\n",
    "\n",
    "with open(\"knn\", 'rb') as f:\n",
    "    knn = pickle.load(f)\n",
    "with open(\"X_train\", 'rb') as f:\n",
    "    X_train = pickle.load(f)\n",
    "\n",
    "fg, ax = plt.subplots(2, 2, figsize=(6, 60))\n",
    "\n",
    "for i in range(1, 3):\n",
    "    img = cv.imread('{0}.jpg'.format(index[i-1]))\n",
    "    img_gray = cv.imread('{0}.jpg'.format(index[i-1]), 0)\n",
    "    new_img = cv.resize(img_gray, (32, 32), interpolation=cv.INTER_NEAREST)\n",
    "    knn.predict([new_img.ravel()])\n",
    "\n",
    "    ax[i - 1, 0].imshow(img)\n",
    "    ax[i - 1, 1].set_title(str(knn.predict([new_img.ravel()])))\n",
    "    ax[i - 1, 1].imshow(new_img, cmap='gray')\n",
    "\n",
    "#plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e3785a6-6dcc-4442-a6b8-a0676a712e90",
   "metadata": {},
   "source": [
    "参考答案"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26903e96-6c9b-4bf3-bf2a-395324b0dcd6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "import cv2 as cv\n",
    "import matplotlib.pyplot as plt\n",
    "from imutils import paths\n",
    "import numpy as np\n",
    "\n",
    "# 随机抽取一张图片\n",
    "img_file = np.random.choice(list(paths.list_images(\".\")),1)[0]\n",
    "\n",
    "# 把图片转化成特征向量\n",
    "img0=cv.imread(img_file,0)\n",
    "img=cv.resize(img0, (32, 32))\n",
    "img=img.ravel()\n",
    "\n",
    "# 载入已经创建的 knn 和 X_train 模型\n",
    "with open(\"knn\", 'rb') as f:\n",
    "    knn = pickle.load(f)\n",
    "with open(\"X_train\", 'rb') as f:\n",
    "    X_train = pickle.load(f)\n",
    "\n",
    "# 预测图片的标签，并得到最近邻的那张图片\n",
    "label=knn.predict([img.ravel()])\n",
    "\n",
    "# 显示这两张图片\n",
    "fg,ax=plt.subplots(1,2)\n",
    "ax[0].imshow(img0,cmap='gray')\n",
    "ax[1].imshow(img.reshape(32,32),cmap='gray')\n",
    "ax[1].set_title(label,size=20)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
